#!/usr/bin/env python
import hashlib
import logging
import os
import shutil
import subprocess
import tempfile
import urllib2
import xml.dom.minidom

import ubiqis.exceptions
import ubiqis.names
from ubiqis import ubimake, util


# TODO(meastman): split this up into managable pieces
def really_load_package(cache_dir, name, req_signatures):
    """Load an ubiqis package."""

    try:
        pkg = ubiqis.names.expand_latest(name)
        meta_url = ubiqis.names.pkg2url(pkg)
    except ubiqis.exceptions.InvalidPackageName, e:
        raise ubiqis.exceptions.PackageLoadError('Invalid package name')

    pkg_pieces = pkg.split('.')
    parent_dir = os.path.join(cache_dir, *pkg_pieces[:-1])
    inst_dir = os.path.join(cache_dir, *pkg_pieces)
    meta_file = os.path.join(parent_dir, pkg_pieces[-1] + '.ubiq')

    if os.path.exists(meta_file):
        return pkg

    fd, tf1_name = tempfile.mkstemp()
    os.close(fd)
    fd, tf2_name = tempfile.mkstemp()
    os.close(fd)

    # download the .ubiq file
    try:
        logging.debug('call ubiload')
        p = subprocess.Popen("ubiload '"+meta_url+"'",
                             shell=True, 
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        retcode = p.wait()

        if retcode < 0:
            logging.error("ubiload was terminated by signal %d" % -retcode)
        elif retcode != 0:
            logging.debug("ubiload returned %d" % retcode)
            for line in p.stderr:
                logging.error(line)
        else:
            logging.debug('Download: %s', meta_url)
            tf_stream = file(tf1_name, 'w')

            stream = p.stdout
            chunk = stream.read(1024)
            while chunk != '':
                tf_stream.write(chunk)
                chunk = stream.read(1024)

            tf_stream.close()

    except urllib2.HTTPError, e:
        raise ubiqis.exceptions.PackageLoadError(e.message)

    # Verify the signed ubiq file
    try:
        if int(req_signatures) > 0 and not util.verify_sig(tf1_name):
            raise ubiqis.exceptions.PackageLoadError('Error verifying '
                                                     'signature')
    except Exception, e:
        logging.error('Verify: %s', e.message)
        raise ubiqis.exceptions.PackageLoadError('Could not find signed ubiq')

    # parse the .ubiq file; check "name", "url" or "suffix", and "hash"
    try:
        meta = xml.dom.minidom.parse(tf1_name)
        if meta.firstChild.nodeName != 'ubiqis':
            raise ubiqis.exceptions.PackageLoadError('Meta-data root node is '
                'not ubiqis')

        names = []
        urls = []
        suffixes = []
        hashes = {}

        for n in meta.firstChild.childNodes:
            if n.nodeType == n.ELEMENT_NODE:
                if n.nodeName == 'name':
                    names.append(n.firstChild.data.strip())
                elif n.nodeName == 'url':
                    urls.append(n.firstChild.data.strip())
                elif n.nodeName == 'suffix':
                    suffixes.append(n.firstChild.data.strip())
                elif n.nodeName == 'hash':
                    hash_type = n.getAttribute('type')
                    if hash_type == '':
                        raise ubiqis.exceptions.PackageLoadError('Meta-data '
                            'contains hash with no type')
                    elif hash_type in hashes:
                        raise ubiqis.exceptions.PackageLoadError('Meta-data '
                            'contains multiple hashes with same type')
                    else:
                        hashes[hash_type] = n.firstChild.data.strip()

    except Exception, e:
        raise ubiqis.exceptions.PackageLoadError('Error while parsing '
            'meta-data')

    # handle name
    if len(names) != 1:
        raise ubiqis.exceptions.PackageLoadError('Package meta-data does not '
            'contain exactly one name')

    if names[0] != pkg:
        raise ubiqis.exceptions.PackageLoadError('Package was requested with '
            'unauthoritative name')

    # handle suffix / url
    if len(suffixes) == 0 and len(urls) == 0:
        raise ubiqis.exceptions.PackageLoadError('Package meta-data contains '
            'no URLs')

    if len(suffixes) == 0:
        suffix = urls[0].split('/')[-1].split('.')[-1]
    elif len(suffixes) == 1:
        suffix = suffixes[0]
    else:
        raise ubiqis.exceptions.PackageLoadError('Package meta-data contains '
            'multiple suffixes')

    if suffix == 'ubiq':
        raise ubiqis.exceptions.PackageLoadError('Package data uses .ubiq')

    if len(urls) == 0:
        urls.append(meta_url.rstrip('ubiq') + suffix)

    pkg_file = meta_file.rstrip('ubiq') + suffix

    # handle hash
    # map from hash types names in ubiqis files to hashlib names
    known_hash_types = {'md5': 'md5', 'sha1': 'sha1'}
    hash_type = None
    hash_value = None

    for t_hash_type in hashes:
        if t_hash_type in known_hash_types:
            hash_type = known_hash_types[t_hash_type]
            hash_value = hashes[t_hash_type]
            break
    else:
        raise ubiqis.exceptions.PackageLoadError('Package meta-data does not '
            'specify a known hash type')

    # download the actual package
    errmsg = 'No URLs found'
    for url in urls:
        try:
            stream = urllib2.urlopen(url)
            tf_stream = file(tf2_name, 'w')
            hash_obj = hashlib.new(hash_type)

            chunk = stream.read(1024)
            while chunk != '':
                hash_obj.update(chunk)
                tf_stream.write(chunk)
                chunk = stream.read(1024)

            tf_stream.close()

        except urllib2.HTTPError, e:
            errmsg = 'HTTP error %d while retrieving package' % e.code
            continue

        if hash_obj.hexdigest() != hash_value:
            errmsg = 'Invalid %s hash' % hash_type
            continue

        if not os.path.exists(parent_dir):
            os.makedirs(parent_dir)
        shutil.move(tf1_name, meta_file)
        shutil.move(tf2_name, pkg_file)

        ### FIXME ### Get rid of the hopping around
        old_dir = os.getcwd()
        os.chdir(cache_dir)
        util.unzip(pkg_file)
        os.chdir(inst_dir)
        logging.debug('run ubimake...')
        ubimake.make()
        os.chdir(old_dir)

        errmsg = None
        break

    if os.path.exists(tf1_name):
        os.unlink(tf1_name)
    if os.path.exists(tf2_name):
        os.unlink(tf2_name)

    if errmsg is None:
        return pkg
    else:
        raise ubiqis.exceptions.PackageLoadError(errmsg)

